;; supercollider/uspec.scm - (c) rohan drape, 2003-2005

;; USpecs describe UGens.  A Uspec is a list (NAME INPUTS OUTPUTS
;; SPECIAL-INDEX [RATE-LIST]).

;; An INPUT is a duple (NAME DEFAULT-VALUE).  If the default value of
;; the last input is a list that input is flattened and sent as
;; multiple inputs to the UGen, which must accept a variable number of
;; inputs.  The output descriptor is normally a list that describes
;; the rate of each output port, values are either rate specifiers or
;; the symbol 'ugen-rate' to indicate the output rate should be the
;; same as the UGen rate.  If the output descriptor is instead an
;; integer, the UGen has a variable number of outputs, and the value
;; indicates the input to read the number of outputs from.  All
;; outputs will be at the rate of the UGen, and the input describing
;; the number of outputs will de removed from the input list.  If
;; RATE-LIST is given it should be a list of integer rate specifiers.
;; It defaults to (2 1 0).

;; Get the name at `s'.  The naming situation is complicated by two
;; issues: all unary and binary math operators are implemented by the
;; UGens 'UnaryMathOp' and 'BinaryMathOp'.  There are name conflicts
;; between the operator names as given in the sc3 code base and
;; provided UGens (Rand etc.).  If there are two names, ie. a user and
;; an implementation name, use `which' to select which one.  The user
;; form strips the _ prefix from the name - it would be better, I
;; guess, if the name were a triple in that case: "SinOsc" ("Mul"
;; "_Mul" "BinaryOpUGen").

(define (uspec-name/ugen s)
  (let ((n (car s)))
    (if (list? n) (cadr n) n)))

(define (uspec-name/user s)
  (let ((n (car s)))
    (if (list? n) (string-drop (car n) 1) n)))

(define (uspec-name/constructor s)
  (let ((n (car s)))
    (if (list? n) (car n) n)))

;; USpec accessors.

(define (uspec-inputs s) (second s))
(define (uspec-outputs s) (third s))
(define (uspec-special-index s) (fourth s))
(define (uspec-rates s) (if (>= (length s) 5) (fifth s) (list 2 1 0)))

;; Variadic predicates.

(define (uspec-has-variable-inputs? s)
  (list? (uspec-input-default-value (last (uspec-inputs s)))))

(define (uspec-has-variable-outputs? s)
  (not (list? (uspec-outputs s))))

;; USpec Input accessors.

(define (uspec-input-name i) (first i))
(define (uspec-input-default-value i) (second i))

;; Construct a usage list for the USpec `s'.

(define (uspec-usage s . defaults)
  (cons (string->symbol (uspec-name/user s))
	(if (null? defaults)
	    (map uspec-input-name (uspec-inputs s))
	    (uspec-inputs s))))

;; Returns a list of five values. 1. The integer number of fixed
;; inputs at the language level.  2. The integer number of fixed
;; inputs at the UGen level. This is not equal to 1 iff one of the
;; inputs at the language level is used to specify the number of
;; outputs for the UGen.  3. A boolean indicating if the UGen accepts
;; a variable number of inputs following the fixed inputs.  4. An
;; integer indicating the number of fixed outputs at the UGen. 5. A
;; boolean indicating if a variable number of outputs may follow the
;; fixed number of outputs.

(define (uspec-arity s)
  (let* ((inputs (uspec-inputs s))
	 (basic-arity (length inputs))
	 (variable-arity (if (null? inputs) 
			     #f
			     (list? (uspec-input-default-value (last inputs)))))
	 (fixed-arity (if variable-arity (- basic-arity 1) basic-arity))
	 (outputs (ref s 2))
	 (output-arity (cond ((list? outputs)
			      (length outputs))
			     ((integer? outputs)
			      #f)
			     (else
			      (error "Illegal USpec" s)))))
    (list fixed-arity
	  (if output-arity fixed-arity (- fixed-arity 1))
	  variable-arity
	  (if output-arity output-arity 0)
	  (if output-arity #f #t))))

;; Return a <list> of known <uspec>s.

(define (uspec-known)
  (append (uspec-sc3) (uspec-math)))

;; Locate a <uspec> value for the USpec with <string> `name'.

(define (uspec-locate name)
  (find (lambda (u) 
	  (string= name (uspec-name/user u)))
	(uspec-known)))
